package com.fourhats.services.impl;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.beanutils.BeanToPropertyValueTransformer;
import org.apache.commons.collections.CollectionUtils;
import org.dozer.Mapper;
import org.springframework.beans.factory.annotation.Autowired;

import com.fourhats.dao.AnswerDAO;
import com.fourhats.dao.MarketDAO;
import com.fourhats.dao.ProductDAO;
import com.fourhats.dao.QuestionDAO;
import com.fourhats.dao.QuestionableObjectDAO;
import com.fourhats.dao.UserDAO;
import com.fourhats.dao.VisitEntityDAO;
import com.fourhats.exception.NotAllowedAnonymousException;
import com.fourhats.exception.UserNotOwerOfProductException;
import com.fourhats.model.Answer;
import com.fourhats.model.Market;
import com.fourhats.model.ObjectTypes;
import com.fourhats.model.Product;
import com.fourhats.model.Question;
import com.fourhats.model.QuestionableObject;
import com.fourhats.model.User;
import com.fourhats.model.VisitEntity;
import com.fourhats.services.QuestionService;
import com.fourhats.services.dto.AnswerDto;
import com.fourhats.services.dto.QuestionDto;
import com.fourhats.services.util.Page;

public class QuestionServiceImpl implements QuestionService {

	@Autowired
	private Mapper mapper;
	
	@Autowired
	private QuestionableObjectDAO questionableObjectDAO;
	
	@Autowired
	private QuestionDAO questionDAO;
	
	@Autowired
	private AnswerDAO answerDAO;
	
	@Autowired
	private UserDAO userDAO;
	
	@Autowired
	private ProductDAO productDAO;
	
	@Autowired
	private MarketDAO marketDAO;
	
	@Autowired
	private VisitEntityDAO visitEntityDAO;
	
	public List<QuestionDto> getQuestions(long entityId, int entityType) {
		return this.getQuestionDtos(this.questionDAO.getQuestions(entityId, entityType));
	}
	
	public List<QuestionDto> getQuestionsWithAnswers(long entityId, int entityType, int quantityAnswers) {
		List<QuestionDto> questions = this.getQuestionDtos(this.questionDAO.getQuestions(entityId, entityType));
		for(QuestionDto question : questions) {
			Page<AnswerDto> answers = this.getAnswer(question.getId(), 1, quantityAnswers);
			question.setAnswers(answers);
		}
		
		return questions;
	}
	
	public Page<AnswerDto> getAnswer(long questionId, int pageNumber, int pageSize) {
		return this.getPage(this.answerDAO.getAnswersByQuestionId(questionId), pageNumber, pageSize);
	}

	public QuestionDto createQuestion(String description, long userId, long entityId, int entityType) throws NotAllowedAnonymousException {
		QuestionableObject questionableObject = this.getOrCreateQuestionableObject(entityId, entityType);
		User user = this.userDAO.getById(userId);
		if(user == null) {
			throw new NotAllowedAnonymousException();
		}
		
		Question question = new Question(description, questionableObject, user);
		this.questionDAO.add(question);
		
		return this.getQuestionDto(question);
	}
	
	public AnswerDto answerQuestion(String description, long userId, long questionId) throws NotAllowedAnonymousException, UserNotOwerOfProductException {
		Question question = this.questionDAO.getById(questionId);
		User user = this.userDAO.getById(userId);
		if(user == null) {
			throw new NotAllowedAnonymousException();
		} else if(user.equals(question.getUser())) {
			throw new UserNotOwerOfProductException();
		}
		
		Answer answer = new Answer(description, question, user);
		this.answerDAO.add(answer);
		
		return this.getAnswerDto(answer);
	}
	
	public boolean hasUnvisitedQuestions(long userId) {
		//ver si tiene preguntas sin contestar en algun producto
		Market market = this.marketDAO.getMarketByUser(userId);
		
		if(market != null) {
			List<Product> products = this.productDAO.getProductsByMarket(market.getId());
			for(Product product : products) {
				List<Question> questions = this.questionDAO.getQuestions(product.getId(), ObjectTypes.PRODUCT.getValue());
				for(Question question : questions) {
					if(!this.answerDAO.hasAnswers(question.getId())) {
						return true;
					}
				}
			}
		}
		
		//ver si no vio alguna respuesta a sus preguntas
		VisitEntity visitEntity = this.visitEntityDAO.getLastByUserAndType(userId, ObjectTypes.QUESTION_PROFILE.getValue());
		List<Question> ownQuestions = this.questionDAO.getQuestionByUser(userId);
		for(Question question : ownQuestions) {
			if(visitEntity == null && this.answerDAO.hasAnswers(question.getId())) {
				return true;
			} else if(this.answerDAO.hasAnswersSince(question.getId(), visitEntity.getDate())) {
				return true;
			}
		}
		
		return false;
	}

	@SuppressWarnings("unchecked")
	public Page<QuestionDto> getQuestionsToUser(long userId, int pageNumber, int pageSize) {
		Market market = marketDAO.getMarketByUser(userId);
		if(market == null){
			return new Page<QuestionDto>();
		}
		
		List<Product> products = productDAO.getProductsByMarket(market.getId());
		
		List<Long> questionableObjectIds = (List<Long>) CollectionUtils.collect(products, new BeanToPropertyValueTransformer("id"));
		List<Question> questions = questionDAO.getQuestionsByQuestionableEntityIds(questionableObjectIds);
		
		return getQuestionPage(questions, pageNumber, pageSize);
	}
	
	public QuestionDto getQuestionById(long questionId) {
		Question question = this.questionDAO.getById(questionId);
		return this.getQuestionDto(question);
	}
	
	public Page<QuestionDto> getQuestionsMadeByUser(long userId, int pageNumber, int pageSize) {
		List<Question> questions = questionDAO.getQuestionByUser(userId);
		return getQuestionPage(questions, pageNumber, pageSize);
	}

	private List<QuestionDto> populateQuestions(List<QuestionDto> questions) {
		for(QuestionDto question : questions) {
			Page<AnswerDto> answers = this.getAnswer(question.getId(), 1, 1);
			//TODO: Por ahora todas las preguntas son de un producto. Cuando no solo sean de productos evaluar con entityType.
			Product product = productDAO.getById(question.getQuestionableObject().getEntityId());
			question.setAnswers(answers);
			question.setQuestionSource(product.getName());
		}
		
		return questions;
	}
	
	private QuestionableObject getOrCreateQuestionableObject(long entityId, int entityType) {
		QuestionableObject qo = this.questionableObjectDAO.getBy(entityId, entityType);
		if(qo == null) {
			qo = new QuestionableObject(entityId, entityType);
			this.questionableObjectDAO.add(qo);
		}
		
		return qo;
	}
	
	private List<AnswerDto> getDtos(List<Answer> answers) {
		List<AnswerDto> dtos = new ArrayList<AnswerDto>();
		for(Answer a : answers) {
			dtos.add(this.getAnswerDto(a));
		}
		
		return dtos;
	}
	
	private List<QuestionDto> getQuestionDtos(List<Question> questions) {
		//TODO: Ver porque se hace el mapping a mano y no con dozzer.
		List<QuestionDto> dtos = new ArrayList<QuestionDto>();
		for(Question q : questions) {
			dtos.add(this.getQuestionDto(q));
		}
		
		return  this.populateQuestions(dtos);
	}
	
	private QuestionDto getQuestionDto(Question question) {
		return mapper.map(question, QuestionDto.class);
	}
	
	private AnswerDto getAnswerDto(Answer answer) {
		return mapper.map(answer, AnswerDto.class);
	}
	
	@SuppressWarnings("unchecked")
	private Page<AnswerDto> getPage(List<Answer> answers, int pageNumber, int pageSize) {
		int first = (pageNumber - 1) * pageSize;
		int last = pageNumber * pageSize > answers.size() ? answers.size() : pageNumber * pageSize;
		return (Page<AnswerDto>) Page.create(this.getDtos(answers.subList(first, last)), pageNumber, pageSize, answers.size());
	}
	
	@SuppressWarnings("unchecked")
	private Page<QuestionDto> getQuestionPage(List<Question> questions, int pageNumber, int pageSize) {
		int first = (pageNumber - 1) * pageSize;
		int last = pageNumber * pageSize > questions.size() ? questions.size() : pageNumber * pageSize;
		return (Page<QuestionDto>) Page.create(this.getQuestionDtos(questions.subList(first, last)), pageNumber, pageSize, questions.size());
	}
}